#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "cgi.h"
#include "liso_engine.h"
#include "http_parser.h"
#include "liso_logging.h"

int build_env(char *envp[], client_conn_t *cliconn, select_engine_t *engine);
char *make_string(char *);
int get_req_body(client_conn_t *cliconn);

/* error messages stolen from: http://linux.die.net/man/2/execve */
void execve_error_handler()
{
    switch (errno)
    {
        case E2BIG:
            fprintf(stderr, "The total number of bytes in the environment \
(envp) and argument list (argv) is too large.\n");
            return;
        case EACCES:
            fprintf(stderr, "Execute permission is denied for the file or a \
script or ELF interpreter.\n");
            return;
        case EFAULT:
            fprintf(stderr, "filename points outside your accessible address \
space.\n");
            return;
        case EINVAL:
            fprintf(stderr, "An ELF executable had more than one PT_INTERP \
segment (i.e., tried to name more than one \
interpreter).\n");
            return;
        case EIO:
            fprintf(stderr, "An I/O error occurred.\n");
            return;
        case EISDIR:
            fprintf(stderr, "An ELF interpreter was a directory.\n");
            return;
        case ELIBBAD:
            fprintf(stderr, "An ELF interpreter was not in a recognised \
format.\n");
            return;
        case ELOOP:
            fprintf(stderr, "Too many symbolic links were encountered in \
resolving filename or the name of a script \
or ELF interpreter.\n");
            return;
        case EMFILE:
            fprintf(stderr, "The process has the maximum number of files \
open.\n");
            return;
        case ENAMETOOLONG:
            fprintf(stderr, "filename is too long.\n");
            return;
        case ENFILE:
            fprintf(stderr, "The system limit on the total number of open \
files has been reached.\n");
            return;
        case ENOENT:
            fprintf(stderr, "The file filename or a script or ELF interpreter \
does not exist, or a shared library needed for \
file or interpreter cannot be found.\n");
            return;
        case ENOEXEC:
            fprintf(stderr, "An executable is not in a recognised format, is \
for the wrong architecture, or has some other \
format error that means it cannot be \
executed.\n");
            return;
        case ENOMEM:
            fprintf(stderr, "Insufficient kernel memory was available.\n");
            return;
        case ENOTDIR:
            fprintf(stderr, "A component of the path prefix of filename or a \
script or ELF interpreter is not a directory.\n");
            return;
        case EPERM:
            fprintf(stderr, "The file system is mounted nosuid, the user is \
not the superuser, and the file has an SUID or \
SGID bit set.\n");
            return;
        case ETXTBSY:
            fprintf(stderr, "Executable was open for writing by one or more \
processes.\n");
            return;
        default:
            fprintf(stderr, "Unkown error occurred with execve().\n");
            return;
    }
}

int do_CGI(int fd, client_conn_t *cliconn, void *self)
{
	select_engine_t *engine = (select_engine_t *) self;
    pid_t pid;
    int stdin_pipe[2];
    int stdout_pipe[2];
    //char buf[BUF_SIZE];
    //int readret;

    /*************** BEGIN PIPE **************/
    /* 0 can be read from, 1 can be written to */
    if (pipe(stdin_pipe) < 0)
    {
        fprintf(stderr, "Error piping for stdin.\n");
        return -1;
    }

    if (pipe(stdout_pipe) < 0)
    {
        fprintf(stderr, "Error piping for stdout.\n");
        return -1;
    }
    /*************** END PIPE **************/

    /*************** BEGIN FORK **************/
    pid = fork();
    /* not good */
    if (pid < 0)
    {
        fprintf(stderr, "Something really bad happened when fork()ing.\n");
        exit(EXIT_FAILURE);
    }

    /* child, setup environment, execve */
    if (pid == 0)
    {
		char *ARGV[MAX_ARGV_NUM];
		char *ENVP[MAX_ENVP_NUM];
		/* build variables */
		ARGV[0] = engine->cgi;
		ARGV[1] = NULL;
	
		build_env(ENVP, cliconn, engine);
        /*************** BEGIN EXECVE ****************/
        close(stdout_pipe[0]);
        close(stdin_pipe[1]);
        dup2(stdout_pipe[1], fileno(stdout));
        dup2(stdin_pipe[0], fileno(stdin));
        /* you should probably do something with stderr */

        /* pretty much no matter what, if it returns bad things happened... */
        if (execve(engine->cgi, ARGV, ENVP))
        {
            execve_error_handler();
            fprintf(stderr, "Error executing execve syscall.\n");
            exit(EXIT_FAILURE);
        }
        /*************** END EXECVE ****************/ 
    }

    if (pid > 0)
    {
        close(stdout_pipe[1]);
        close(stdin_pipe[0]);

		if (cliconn->method == HP_MTHD_POST) {
			if (get_req_body(cliconn))
				return -1;

			if (write(stdin_pipe[1], cliconn->recvbuf->bufptr, 
						cliconn->contentlen) < 0) {
				fprintf(stderr, "Error writing to spawned CGI program.\n");
				return -1;
			}
		}

        close(stdin_pipe[1]); /* finished writing to spawn */

		liso_engine_set_pipe2sock(stdout_pipe[0], fd);
		return 0;
   }
    /*************** END FORK **************/

	fprintf(stderr, "Damn it! How did you get here?!");
	return -1;
}

int build_env(char *envp[], client_conn_t *cliconn, select_engine_t *engine)
{
	int index = 0;
	char buf[1024];
	char *ptr, *line;
	line = "";

	envp[index++] = make_string("GATEWAY_INTERFACE=CGI/1.1");
	envp[index++] = make_string("SERVER_PROTOCOL=HTTP/1.1");
	envp[index++] = make_string("SERVER_SOFTWARE=Liso/1.0");
	sprintf(buf, "SCRIPT_NAME=%s", engine->cgi);
	envp[index++] = make_string(buf);
	sprintf(buf, "REMOTE_ADDR=%s", inet_ntoa(cliconn->clientaddr.sin_addr));
	envp[index++] = make_string(buf);
	sprintf(buf, "SERVER_PORT=%d", cliconn->port);
	envp[index++] = make_string(buf);
	sprintf(buf, "REQUEST_URI=%s", cliconn->uri);
	envp[index++] = make_string(buf);
	sprintf(buf, "PATH_INFO=%s", cliconn->uri+strlen(CGI_PATH)-1);
	envp[index++] = make_string(buf);
	/* build request method */
	switch (cliconn->method) {
		case HP_MTHD_GET:
			envp[index++] = make_string("REQUEST_METHOD=GET");
			break;
		case HP_MTHD_POST:
			envp[index++] = make_string("REQUEST_METHOD=POST");
			break;
		case HP_MTHD_HEAD:
			envp[index++] = make_string("REQUEST_METHOD=HEAD");
			break;
	}
	/* build query string */
	if ((ptr = strchr(cliconn->uri, '?')) != NULL) {
		sprintf(buf, "QUERY_STRING=%s", ptr+1);
		envp[index++] = make_string(buf);
	}
	/* build request header info */
	while (strcmp(line, "\r")) {
		if((line = liso_buf_readline(cliconn->recvbuf)) == NULL) {
			liso_logging_log("cgi", "build_env", "Bad Request format.\n");
			return -1;
		}
		else if (!strncasecmp(line, "Content-Length", strlen("Content-Length"))) {
			sprintf(buf, "CONTENT_LENGTH=%s", line+strlen("Content-Length")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Content-Type", strlen("Content-Type"))) {
			sprintf(buf, "CONTENT_TYPE=%s", line+strlen("Content-Type")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Accept", strlen("Accept"))) {
			sprintf(buf, "HTTP_ACCEPT=%s", line+strlen("Accept")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Referer", strlen("Referer"))) {
			sprintf(buf, "HTTP_REFERER=%s", line+strlen("Referer")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Accept-Encoding", strlen("Accept-Encoding"))) {
			sprintf(buf, "HTTP_ACCEPT_ENCODING=%s", line+strlen("Accept-Encoding")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Accept-Language", strlen("Accept-Language"))) {
			sprintf(buf, "HTTP_ACCEPT_LANGUAGE=%s", line+strlen("Accept-Language")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Accept-Charset", strlen("Accept-Charset"))) {
			sprintf(buf, "HTTP_ACCEPT_CHARSET=%s", line+strlen("Accept-Charset")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Cookie", strlen("Cookie"))) {
			sprintf(buf, "HTTP_COOKIE=%s", line+strlen("Cookie")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "User-Agent", strlen("User-Agent"))) {
			sprintf(buf, "HTTP_USER_AGENT=%s", line+strlen("User-Agent")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Connection", strlen("Connection"))) {
			sprintf(buf, "HTTP_CONNECTION=%s", line+strlen("Connection")+2);
			envp[index++] = make_string(buf);
		}
		else if (!strncasecmp(line, "Host", strlen("Host"))) {
			sprintf(buf, "HTTP_HOST=%s", line+strlen("Host")+2);
			envp[index++] = make_string(buf);
		}
	}
	envp[index++] = NULL;
	return 0;
}

char *make_string(char *str)
{
	int len = strlen(str) + 1;
	char *newstr = (char *) malloc(len);
	if (newstr == NULL)
		return NULL;
	else {
		strcpy(newstr, str);
		return newstr;
	}
}

int get_req_body(client_conn_t *cliconn)
{
	char *line = "";
	cliconn->contentlen = -1;

	while (strcmp(line, "\r")) {
		if((line = liso_buf_readline(cliconn->recvbuf)) == NULL) {
			liso_logging_log("cgi", "get_req_body", "Bad Request format.\n");
			return -1;
		}
		if (!strncasecmp(line, "Content-Length", strlen("Content-Length"))) {
			cliconn->contentlen = atoi(line+strlen("Content-Length: "));
		}
	}

	if (cliconn->contentlen == -1)
		return -1;
	else
		return 0;
}
